import { Box2, BufferAttribute, BufferGeometry, DataTexture, FloatType, LinearFilter, NearestFilter, Points, RedFormat, ShaderMaterial, TextureFilter, Vector2 } from 'three'
import { CqkjMap } from '../Basic/CqkjMap'
import { DataElement } from '../Basic/DataElement'

import { getValue, OneDimensional } from '../Util/common'
import { readJSONFromImg, GridDataInfo } from '../Util/readJsonFromPng'
import { PROJECTION } from '../Util/static'


export interface Grid2DataOption {
  data: Float32Array
  box: Box2
  filter: TextureFilter
  size: Vector2
}

export class Grid2Data extends DataElement<Grid2DataOption> {
  /** 数据贴图 */
  get texture () {
    return getValue(this._cache, 'texture', () => {
      const { data, size, filter } = this.option
      const texture = new DataTexture(data, size.x, size.y)
      texture.format = RedFormat
      texture.type = FloatType
      texture.minFilter = filter
      texture.magFilter = filter
      texture.unpackAlignment = 1
      texture.needsUpdate = true
      return texture
    })
  }

  get xToI () {
    return getValue(this._cache, 'xToI', () => {
      const { box, size } = this.option
      return new OneDimensional([box.min.x, box.max.x], [-0.5, size.x - 0.5])
    })
  }

  get xToU () {
    return getValue(this._cache, 'xToU', () => {
      const { box } = this.option
      return new OneDimensional([box.min.x, box.max.x])
    })
  }

  get yToJ () {
    return getValue(this._cache, 'yToJ', () => {
      const { box, size } = this.option
      return new OneDimensional([box.min.y, box.max.y], [-0.5, size.y - 0.5])
    })
  }

  get yToV () {
    return getValue(this._cache, 'yToV', () => {
      const { box } = this.option
      return new OneDimensional([box.min.y, box.max.y])
    })
  }

  get max () {
    return getValue(this._cache, 'max', () => {
      const { data } = this.option
      let max = data[0]
      for (let i = 1; i < data.length; i++) {
        if (max < data[i]) max = data[i]
      }
      return max
    })
  }

  get min () {
    return getValue(this._cache, 'min', () => {
      const { data } = this.option
      let min = data[0]
      for (let i = 1; i < data.length; i++) {
        if (min > data[i]) min = data[i]
      }
      return min
    })
  }

  debug (map: CqkjMap): void {
    const { texture, min, max, xToI, yToJ, xToU, yToV } = this
    const { size } = this.option

    const geometry = new BufferGeometry()
    const position = new Float32Array(size.x * size.y * 3)
    const uv = new Float32Array(size.x * size.y * 2)
    const z=PROJECTION.altToZ(10000)
    for (let j = 0, _ = 0; j < size.y; j++) {
      const y = yToJ.backward(j)
      const v = yToV.forward(y)
      for (let i = 0; i < size.x; i++, _++) {
        const x = xToI.backward(i)
        const u = xToU.forward(x)
        const _3 = _ * 3
        const _2 = _ * 2
        position[_3] = x
        position[_3 + 1] = y
        position[_3 + 2] = z
        uv[_2] = u
        uv[_2 + 1] = v
      }
    }
    geometry.attributes.position = new BufferAttribute(
      position,
      3
    )
    geometry.attributes.uv = new BufferAttribute(
      uv,
      2
    )

    const mesh = new Points(geometry, new ShaderMaterial({
      transparent: true,
      depthWrite: false,
      depthTest: false,
      uniforms: {
        u_data: {
          value: texture
        },
        u_min: {
          value: min
        },
        u_max: {
          value: max
        }
      },
      vertexShader: `
      varying vec2 v_uv;
      void main(){
          v_uv=uv;
          gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
          gl_PointSize=8.0;
      }
      `,
      fragmentShader: `
      varying vec2 v_uv;
      uniform sampler2D u_data;
      uniform float u_min;
      uniform float u_max;
      void main() { 
        float v=(texture2D(u_data,v_uv).r-u_min)/(u_max-u_min);
        gl_FragColor=mix(vec4(0.0,1.0,0.0,1.0),vec4(1.0,0.0,0.0,1.0),v);    
      } 
      `
    }))
    map.scene.add(mesh)
  }

  /**
   * 通过图片数据构建网格数据
   * @param url 图片地址
   * @param discrete 格点是否是离散的（离散的会渲染成方格图）
   * @param size 网格尺寸[列数，行数],默认使用图片数据原始的行列数
   * @returns
   */
  static async fromPng (url: string, discrete = false, size?: [number, number]) {
    if (url) {
      const grid = await readJSONFromImg(url, 1)
      if (grid) {
        return new Grid2Data(dealGridInfo(grid, discrete, size))
      }
    }
    return undefined
  }
}

function dealGridInfo (grid: GridDataInfo, discrete = true, size?: [number, number]): Grid2DataOption {
  const size1 = [grid.width, grid.data.length / grid.width]
  const size2 = size ?? size1

  const bottom = PROJECTION.latToY(grid.south)
  const top = PROJECTION.latToY(grid.north)
  const left = PROJECTION.lonToX(grid.west)
  const right = PROJECTION.lonToX(grid.east)

  const iToLon = new OneDimensional([0, size1[0] - 1], [grid.west, grid.east])
  const jToLat = new OneDimensional([0, size1[1] - 1], [grid.south, grid.north])

  const xs: Array<{
    i: number
    k: number
  }> = []

  const dx = (right - left) / (size2[0] - 1)
  for (let _i = 0, x = left; _i < size2[0]; _i++, x += dx) {
    const ik = iToLon.backward(PROJECTION.xToLon(x))
    const i = Math.floor(ik)
    xs.push({
      i,
      k: ik - i
    })
  }

  const ys: Array<{
    j: number
    k: number
  }> = []
  const dy = (top - bottom) / (size2[1] - 1)
  for (let _j = 0, y = bottom; _j < size2[1]; _j++, y += dy) {
    const jk = jToLat.backward(PROJECTION.yToLat(y))
    const j = Math.floor(jk)
    ys.push({
      j,
      k: jk - j
    })
  }

  if (discrete) {
    ys.forEach(e => {
      if (e.k > 0.5) {
        e.j += 1
      }
      e.k = 0
    })
    xs.forEach(e => {
      if (e.k > 0.5) {
        e.i += 1
      }
      e.k = 0
    })
  }

  const data1: number[] = []
  for (let j = 0, offset = 0; j < size1[1]; j++, offset += size1[0]) {
    xs.forEach(x => {
      const v = grid.data[offset + x.i]
      if (x.k > 0.0000000001) {
        const v1 = grid.data[offset + x.i + 1]
        data1.push(v + (v1 - v) * x.k)
      } else {
        data1.push(v)
      }
    })
  }

  const data: number[] = []

  for (let _j = 0; _j < size2[1]; _j++) {
    const { j, k } = ys[_j]
    const offset = j * size2[0]
    if (k > 0.0000000001) {
      for (let i = 0; i < size2[0]; i++) {
        const v1 = data1[offset + i]
        const v2 = data1[offset + i + size2[0]]
        data.push(v1 + (v2 - v1) * k)
      }
    } else {
      for (let i = 0; i < size2[0]; i++) {
        data.push(data1[offset + i])
      }
    }
  }

  return {
    data: new Float32Array(data),
    box: new Box2(new Vector2(left - dx / 2, bottom - dy / 2), new Vector2(right + dx / 2, top + dy / 2)),
    filter: discrete ? NearestFilter : LinearFilter,
    size: new Vector2(size2[0], size2[1])
  }
}
